package com.gintama.common.advice;

import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.util.StrUtil;
import com.gintama.base.constants.BaseConstants;
import com.gintama.base.model.Result;
import com.gintama.common.error.BizException;
import com.gintama.common.error.BusinessException;
import com.gintama.common.error.UnauthorizedUserException;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authz.UnauthorizedException;
import org.slf4j.MDC;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

/**
 * 统一异常处理，后期业务直接抛出对应的异常即可 有统一异常处理统一捕获处理 一般情况下抛出BusinessException 代表业务异常<br>
 * 处理后给前端抛出对应的格式信息 前端可以根据规定信息进行处理 业务方面直接统一往上层抛出异常即可
 * 到Controller端统一处理
 *
 */
@RestControllerAdvice
@Slf4j
public class DefaultErrorAdvice {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BusinessException.class)
    public Result<Void> handleBusinessException(HttpServletRequest re, BusinessException e) {
        String errMsg = StrUtil.nullToDefault(e.getMessage(),  e.getClass().getName());
        String requestUri = re.getRequestURI();
        printBizErrMsg(requestUri, errMsg, e);
        return Result.err4msg(errMsg);
    }

    /**
     * 参数校验失败
     *
     * @param req 请求
     * @param e   异常
     * @return result 响应结果
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Void> handleMethodArgumentNotValidException(HttpServletRequest req,
            MethodArgumentNotValidException e) {
        log.error("触发请求:[{}]时系统出现异常，异常类型：{} 具体异常信息为：{}", req.getRequestURI(), "方法参数无效异常", e);

        BindingResult result = e.getBindingResult();
        List<String> errors = result.getFieldErrors().stream()
                .map(FieldError::getDefaultMessage).collect(Collectors.toList());
        return Result.err4msg(StringUtils.join(errors, ";"));
    }

    /**
     * 约束违例异常
     *
     * @param req 请求
     * @param e   异常
     * @return result 响应结果
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ConstraintViolationException.class)
    public Result<Void> handleConstraintViolationException(HttpServletRequest req, ConstraintViolationException e) {
        log.error("触发请求:[{}]时系统出现异常，异常类型：{} 具体异常信息为：{}", req.getRequestURI(), "约束违例异常", e);
        List<String> errors = e.getConstraintViolations().stream()
                .map(ConstraintViolation::getMessage).collect(Collectors.toList());
        return Result.err4msg(StringUtils.join(errors, ";"));
    }

    /**
     * 未经授权用户异常
     *
     * @param req 请求
     * @param e   异常
     * @return result 响应结果
     */
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    @ExceptionHandler(UnauthorizedUserException.class)
    public Result<Void> handleUnauthorizedUserException(HttpServletRequest req, UnauthorizedUserException e) {
        String errMsg = StrUtil.nullToDefault(e.getMessage(), e.getClass().getName());
        log.error(StrFormatter.format("触发请求:[{}]时系统出现异常，异常类型：{}，具体异常信息为：{}", req.getRequestURI(), "未经授权用户异常", errMsg),
                e);
        return Result.err4msg(errMsg);
    }

    /**
     * 未经授权用户异常
     *
     * @param req HTTP请求
     * @param e   异常
     * @return Void结果 result
     */
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    @ExceptionHandler(org.apache.shiro.authz.UnauthorizedException.class)
    public Result<Void> handleShiroUnauthorizedException(HttpServletRequest req, UnauthorizedException e) {
        String errMsg = StrUtil.nullToDefault(e.getMessage(), e.getClass().getName());
        log.error(StrFormatter.format("触发请求:[{}]时系统出现异常，异常类型：{}，具体异常信息为：{}", req.getRequestURI(), "未经授权用户异常", errMsg),
                e);
        return Result.err4msg(errMsg);
    }

    /**
     * Shiro身份验证异常
     *
     * @param req HTTP请求
     * @param e   异常
     * @return Void结果 result
     */
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    @ExceptionHandler(AuthenticationException.class)
    public Result<Void> handleShiroAuthenticationException(HttpServletRequest req, AuthenticationException e) {
        String errMsg = StrUtil.nullToDefault(e.getMessage(), e.getClass().getName());
        log.error(StrFormatter.format("触发请求:[{}]时系统出现异常，异常类型：{}，具体异常信息为：{}", req.getRequestURI(), "身份验证异常", errMsg), e);
        return Result.err4msg(errMsg);
    }

    /**
     * 权限不足异常
     *
     * @param req 请求
     * @param e   异常
     * @return result 响应结果
     */
    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ExceptionHandler(AccessDeniedException.class)
    public Result<Void> handleAccessDeniedException(HttpServletRequest req, AccessDeniedException e) {
        String errMsg = StrUtil.nullToDefault(e.getMessage(), e.getClass().getName());
        log.error(StrFormatter.format("触发请求:[{}]时系统出现异常，异常类型：{}，具体异常信息为：{}", req.getRequestURI(), "用户权限不足异常", errMsg),
                e);
        return Result.err4msg(errMsg);
    }

    /**
     * 请求路径错误
     *
     * @param req 请求
     * @param e   异常
     * @return result 响应结果
     */
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(NoHandlerFoundException.class)
    public Result<Void> handleNoHandlerFoundException(HttpServletRequest req, NoHandlerFoundException e) {
        String errMsg = StrUtil.nullToDefault(e.getMessage(), e.getClass().getName());
        log.error(StrFormatter.format("触发请求:[{}]时系统出现异常，异常类型：{}，具体异常信息为：{}", req.getRequestURI(), "请求路径错误", errMsg), e);
        return Result.err4msg(errMsg);
    }

    /**
     * 请求路径错误
     *
     * @param req 请求
     * @param e   异常
     * @return result 响应结果
     */
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public Result<Void> handleHttpRequestMethodNotSupportedException(HttpServletRequest req,
            HttpRequestMethodNotSupportedException e) {
        String errMsg = StrUtil.nullToDefault(e.getMessage(), e.getClass().getName());
        log.error(StrFormatter.format("触发请求:[{}]时系统出现异常，异常类型：{}，具体异常信息为：{}", req.getRequestURI(), "请求路径错误", errMsg), e);
        return Result.err4msg(errMsg);
    }

    /**
     * 全局异常处理
     *
     * @param req 请求
     * @param e   异常
     * @return result 响应结果
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Exception.class)
    public Result<Void> globalExceptionHandler(HttpServletRequest req, Exception e) {
        String errMsg = StrUtil.nullToDefault(e.getMessage(), e.getClass().getName()), requestUri = req.getRequestURI();
        if (e instanceof BusinessException || e instanceof BizException) {
            // BusinessException 或 BizException 的子类也按业务异常处理
            printBizErrMsg(requestUri, errMsg, e);
        } else {
            // 全局异常处理
            log.error(StrFormatter.format("触发请求:[{}]时系统出现异常，异常类型：{}，具体异常信息为：{}", requestUri, "全局异常", errMsg), e);
            errMsg = "服务器开小差了，紧急问题请联系客服，请求Id：" + MDC.get(BaseConstants.PTX_ID);
        }
        return Result.err4msg(errMsg);
    }


    /**
     * 业务异常打印
     */
    private void printBizErrMsg(String requestUri, String errMsg, Throwable t) {
        log.error(StrFormatter.format("触发请求:[{}]时系统出现异常，异常类型：{}，具体异常信息为：{}", requestUri, "业务异常", errMsg), t);
    }

}
